package com.hzgj.bcl.soa.rpc.retry;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

import io.netty.util.HashedWheelTimer;

public abstract class RetryPolicy {

    private static final Logger logger = LoggerFactory.getLogger(RetryPolicy.class);
    private static final HashedWheelTimer timer = new HashedWheelTimer();
    protected final int startRetryTime;

    /**
     * Constructor
     *
     * @param startRetryTime start milliseconds before trying retry
     */
    public RetryPolicy(int startRetryTime) {
        this.startRetryTime = startRetryTime;
    }

    /**
     * Does the retry. Will always try all URIs before throwing an exception.
     *
     * @param state        of connection
     * @param retryHandler handles the retry itself
     * @throws RetryCancelled if retry is cancelled
     */
    public final void retry(final InvokeState state, final RetryHandler retryHandler) throws RetryCancelled {
        if (state.retryCount == 0) {
            state.msBeforeRetry = this.startRetryTime;
        }
        if (this.shouldRetry(state)) {
            if (logger.isDebugEnabled()) {
                logger.debug(String.format("Retry %s to handle command", state.retryCount));
            }
            state.retryCount += 1;
            timer.newTimeout(timeout -> {
                try {
                    retryHandler.doRetry();
                } catch (IOException e) {
                    retryHandler.catchException(e);
                }
            }, state.msBeforeRetry, TimeUnit.MILLISECONDS);
        } else {
            throw new RetryCancelled();
        }
    }

    /**
     * Should another retry be attempted according to the policy
     *
     * @param invokeState current connection state
     * @return true if retry should be attempted, false if not.
     */
    public abstract boolean shouldRetry(InvokeState invokeState);

    public static class InvokeState {
        public int retryCount;
        public int msBeforeRetry = 0;
    }

    /**
     * Cancelled retry exception
     */
    public static class RetryCancelled extends Exception {
        private static final long serialVersionUID = 8043829471264975062L;
    }
}